home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
100 Great Games for Palm OS 2
/
PalmV2012301.ISO
/
puzzles
/
Pilot Mines
/
src
/
mine.c
< prev
next >
Wrap
C/C++ Source or Header
|
1999-06-02
|
19KB
|
863 lines
/*
* PilotMines is Copyright (c) 1997, 1998 by Thomas Pundt
*
* Permission to use, copy, modify, and distribute this software for any
* purpose, without fee, and without a written agreement is hereby granted,
* provided that the above copyright notice and this paragraph and the
* following two paragraphs appear in all copies.
*
* IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
* SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
* THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
* BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
* UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
*/
#include <Common.h>
#include <System/KeyMgr.h>
#include <System/SysAll.h>
#include <UI/UIAll.h>
#include "minercp.h"
#include "mine.h"
#include "hscore.h"
/*
bit 5 : Marked
bit 4 : Cover/uncovered
bit 3-0 : number of mines in adjoining cells
*/
#define MINE 0x09
#define COVERED 0x10
#define MARKED 0x20
#define TMPMARK 0x40
#define InArray(x,y) (((x)>=0 && (x)<WIDTH) && ((y)>=0 && (y)<HEIGHT))
#define IsVisible(x,y) ((game.minefield[x][y] & COVERED) ? 0 : 1)
/*
* Game options
*/
#define ShowAll 1
#define AlternateControl 2
#define FirstNoMine 4
static int levelmines[] = { 10, 30, 50, 70 };
static struct { SWord x,y; } stack[WIDTH*HEIGHT];
static int numuncovered, marked, nummines, StckPtr;
static Boolean isHighlighted;
static ULong GameStartedAt;
static WinHandle bitmaps; // Offscreen Window with tile bitmaps
long score = 0;
static DmOpenRef pmDB;
Game game;
/*
* mapping function, that applies f to all neighbours of (x,y)
*/
static int Neighbours (func f, SWord x, SWord y)
{
int res = 0;
res += f(x-1,y-1); res += f(x,y-1); res += f(x+1,y-1);
res += f(x-1,y ); res += f(x+1,y );
res += f(x-1,y+1); res += f(x,y+1); res += f(x+1,y+1);
return res;
}
static int IsMine(SWord x, SWord y)
{
return (InArray(x,y) && (game.minefield[x][y] & 0xf) == MINE);
}
static int IsMarked(SWord x, SWord y)
{
return (InArray(x,y) && (game.minefield[x][y] & MARKED));
}
/*
* computes the number of mines around field (x,y)
*/
static int countMines(int x, int y)
{
return Neighbours (IsMine, x, y);
}
/*
* computes the marked fields (supposed mines) around field (x,y)
*/
static int foundMines(int x, int y)
{
return Neighbours (IsMarked, x, y);
}
/*
* pushes (x,y) on the stack, if field (x,y) isn't marked, isn't
* visible, and isn't already on the stack.
*/
static int push(SWord x, SWord y)
{
if (InArray(x,y) && !(game.minefield[x][y] & (MARKED|TMPMARK)) &&
(game.minefield[x][y] & COVERED)) {
game.minefield[x][y] |= TMPMARK;
stack[StckPtr].x=x;
stack[StckPtr].y=y;
StckPtr++;
}
return StckPtr;
}
/*
* pops the topmost element (x,y) off the stack, and stores it in
* the parameters x and y.
*/
static void pop(SWord *x, SWord *y)
{
StckPtr--;
*x=stack[StckPtr].x;
*y=stack[StckPtr].y;
}
/*
* Draw the cell at coordinates (x,y); if Id==0, look at the contents
* of field (x,y) and decide what pattern to draw; else draw pattern
* Id.
*/
static void DrawCell(SWord x, SWord y, int Id)
{
RectangleType rect;
Byte field = game.minefield[x][y] & ~TMPMARK;
if (!Id) {
if (field & MARKED)
Id = ID_Flag;
else if (field & COVERED)
Id = ID_Covered;
else
Id = ID_Blank+field;
}
// Copy bitmap from offscreen window to active window
rect.topLeft.x = (Id - ID_Flag)*10;
rect.topLeft.y = 0;
rect.extent.x = rect.extent.y = 10;
WinCopyRectangle(bitmaps,WinGetActiveWindow(),&rect,x*10,(y+2)*10,scrCopy);
}
/*
* Draw the "mines left to mark" status information
*/
static void DrawMarked()
{
char buf[2];
buf[0] = (nummines - marked) / 10 + 48;
buf[1] = (nummines - marked) % 10 + 48;
WinDrawInvertedChars(buf, 2, 10, 0);
}
/*
* Draw the "passed time" status information; if a menubar is active,
* don't draw.
*/
static void DrawTime()
{
char buf[5] = "00:00";
ULong actseconds;
MenuBarPtr menu;
if ((menu = MenuGetActiveMenu()) && menu->attr.visible)
return;
actseconds = TimGetSeconds() - GameStartedAt;
if (actseconds>3599)
actseconds = 3599;
buf[4] = actseconds % 10 + 48;
buf[3] = (actseconds / 10) % 6 + 48;
buf[1] = (actseconds / 60) % 10 + 48;
buf[0] = (actseconds / 600) % 10 + 48;
WinDrawInvertedChars(buf, 5, 130, 0);
}
/*
* Draw the field with all elements uncovered.
*/
static void UncoverAll()
{
SWord x,y;
if (!(game.options & ShowAll))
return;
for (y=0; y<HEIGHT; y++)
for (x=0; x<WIDTH; x++)
if (!IsVisible(x,y)) {
game.minefield[x][y] &= 0xf;
DrawCell(x,y,0);
}
}
/*
* Draw the field; is called, if status was restored after an application
* switch.
*/
static void ShowFormMine()
{
SWord x,y;
FrmDrawForm(FrmGetActiveForm());
for (y=0; y<HEIGHT; y++)
for (x=0; x<WIDTH; x++)
DrawCell(x,y,0);
GameStartedAt = TimGetSeconds() - game.seconds;
DrawMarked();
}
/*
* show the contents of field (x,y); "recursively" (well a recursive
* function actually would crash the application :-) uncover all
* neighbours of a field, if it has no mine as neighbour.
*/
static int Show(SWord x, SWord y)
{
push(x,y);
while(StckPtr) {
pop(&x,&y);
game.minefield[x][y] &= ~COVERED;
numuncovered++;
DrawCell(x, y, 0);
if (IsMine(x,y)) {
if (game.done == IsRunning)
game.done = IsLost;
UncoverAll();
} else {
if (game.done == IsRunning && numuncovered + nummines == WIDTH*HEIGHT) {
game.done = IsWon;
score = TimGetSeconds() - GameStartedAt;
if (score>3599) score = 3599;
}
if (game.minefield[x][y] == TMPMARK) {
Neighbours (push, x, y);
}
}
}
return 0;
}
/*
* highlight covered field (x,y). Is called, if you tap on an uncovered field.
*/
static int Highlight(SWord x, SWord y)
{
if (InArray(x,y) && !IsMarked(x,y) && !IsVisible(x,y))
DrawCell(x, y, ID_Gray);
return 0;
}
/*
* undo any highlighting done on field (x,y)
*/
static int unHighlight(SWord x, SWord y)
{
if (InArray(x,y) && !IsMarked(x,y) && !IsVisible(x,y))
DrawCell(x, y, 0);
return 0;
}
/*
* compute values "numuncovered" and "marked" after restoring
* saved status from an application switch.
*/
static void InitBoard()
{
SWord x, y;
WinHandle tmpHandle;
VoidHand bitmapHandle;
BitmapPtr bitmap;
Word err;
numuncovered = 0;
marked = 0;
for (y=0; y<HEIGHT; y++)
for (x=0; x<WIDTH; x++) {
if (IsMarked(x,y))
marked++;
if (IsVisible(x,y))
numuncovered++;
}
bitmaps = WinCreateOffscreenWindow(140, 10, screenFormat, &err);
tmpHandle = WinSetDrawWindow(bitmaps);
for (x=0; x<14; x++) {
bitmapHandle = DmGetResource('Tbmp', ID_Flag+x);
bitmap = MemHandleLock(bitmapHandle);
WinDrawBitmap(bitmap, x*10, 0);
MemHandleUnlock(bitmapHandle);
DmReleaseResource(bitmapHandle);
}
WinSetDrawWindow(tmpHandle);
}
static void SetupBoard(SWord x, SWord y)
{
SWord ix,iy,pieces;
SysRandom(TimGetSeconds());
for (pieces=0; pieces<nummines; pieces++) {
do {
ix = SysRandom(0) % 16;
iy = SysRandom(0) % 14;
} while (IsMine(ix,iy) || ((ix == x) && (iy == y)));
game.minefield[ix][iy] = MINE+COVERED;
}
for (iy=0; iy<HEIGHT; iy++)
for (ix=0; ix<WIDTH; ix++)
if (!IsMine(ix,iy))
game.minefield[ix][iy] = countMines(ix,iy)+COVERED;
}
/*
* Initialize the game. Compute an initial mine layout an make all
* necessary drawings.
*/
static void InitFormMine()
{
SWord x,y;
FrmDrawForm(FrmGetActiveForm());
for (y=0; y<HEIGHT; y++)
for (x=0; x<WIDTH; x++) {
game.minefield[x][y] = COVERED;
DrawCell(x, y, ID_Covered);
}
// Changed by Lucas Bremgartner
if ((game.options & FirstNoMine) == false) {
SetupBoard(255,255);
}
// Changed by Lucas Bremgartner (End)
numuncovered = marked = StckPtr = score = 0;
game.done = IsToBeStarted;
isHighlighted = false;
DrawMarked();
}
/*
* Handle any action necessary, if you tap on field (x,y);
* "ctrl == true" means, you've also pressed the PageUp or PageDown button;
* in that case, a covered unmarked field is marked or a covered marked
* field is unmarked. Else, a covered field is uncovered.
*/
static Boolean HandlePenDownEvent(SWord x, SWord y, Boolean ctrl)
{
if (!InArray(x,y))
return false;
if (game.done == IsToBeStarted) {
// Insert by Lucas Bremgartner
if (game.options & FirstNoMine) {
SetupBoard(x,y);
}
// Insert by Lucas Bremgartner (End)
game.done = IsRunning;
GameStartedAt = TimGetSeconds();
}
switch (ctrl) {
case false:
if (IsVisible(x,y)) {
if (foundMines(x,y) == (game.minefield[x][y] & 0xf)) {
Neighbours (Show, x, y);
} else {
Neighbours (Highlight, x, y);
isHighlighted = true;
}
} else {
if (game.options & AlternateControl) { // "Dylan style controls"
if (!(game.minefield[x][y] & MARKED)) {
game.minefield[x][y] |= MARKED;
DrawCell(x, y, 0);
marked++;
} else {
game.minefield[x][y] &= ~MARKED;
marked--;
Show(x,y);
}
DrawMarked();
} else {
if (!(game.minefield[x][y] & MARKED)) {
Show(x,y);
}
}
}
break;
case true:
if (!IsVisible(x,y)) {
if (!(game.minefield[x][y] & MARKED)) {
game.minefield[x][y] |= MARKED;
DrawCell(x, y, 0);
marked++;
} else {
game.minefield[x][y] &= ~MARKED;
DrawCell(x, y, 0);
marked--;
}
DrawMarked();
}
break;
}
return true;
}
/*
* Dispatch any menu events we have to handle
*/
static Boolean MyHandleMenuEvent(EventPtr e)
{
Boolean handled;
CALLBACK_PROLOGUE
handled = false;
MenuEraseStatus (MenuGetActiveMenu());
switch(e->data.menu.itemID) {
case ID_MenuItem: /* New Game */
InitFormMine();
handled = true;
break;
case ID_MenuItem+10: /* About Pilot Mines */
FrmPopupForm(ID_FrmAbout);
handled = true;
break;
case ID_MenuItem+11: /* Preferences */
FrmPopupForm(ID_FrmPreferences);
handled = true;
break;
case ID_MenuItem+12: /* High Scores */
FrmPopupForm(ID_FrmHighScores);
handled = true;
break;
default:
break;
}
CALLBACK_EPILOGUE
return handled;
}
/*
* dispatch all events we have to handle in the main form (the one
* showing the mine field)
*/
static Boolean MineFormHandleEvent(EventPtr e)
{
Boolean handled;
static SWord saveX, saveY;
CALLBACK_PROLOGUE
handled = false;
if (game.done == Restart)
InitFormMine();
if (game.done == IsRunning)
DrawTime();
// Insert by Lucas Bremgarter
if (game.done == HighScoreWon) {
if (!FrmAlert(GameOver)) {
InitFormMine();
} else {
game.done = IsFinishedWon;
}
}
if (game.done == HighScoreLost) {
if (!FrmAlert(GameOver+1)) {
InitFormMine();
} else {
game.done = IsFinishedLost;
}
}
// Insert by Lucas Bremgartner (End)
switch (e->eType) {
case menuEvent:
handled = MyHandleMenuEvent(e);
break;
case frmOpenEvent:
handled = true;
break;
case keyDownEvent:
switch(e->data.keyDown.chr) {
case 'n':
InitFormMine();
handled = true;
break;
case 'h':
FrmPopupForm(ID_FrmHighScores);
handled = true;
break;
#ifdef ENGLISH
case 'p':
#else
case 'e':
#endif
FrmPopupForm(ID_FrmPreferences);
handled = true;
break;
}
break;
case penDownEvent:
saveX=e->screenX/10;
saveY=e->screenY/10-2;
handled = HandlePenDownEvent(saveX, saveY,
(KeyCurrentState() & (keyBitPageUp | keyBitPageDown))!=0);
switch (game.done) {
case IsWon:
game.done = IsFinishedWon;
if (isHighScore((int)score))
FrmPopupForm(ID_FrmHighScores);
else
if (!FrmAlert(GameOver))
InitFormMine();
break;
case IsLost:
game.done = IsFinishedLost;
if (!FrmAlert(GameOver+1))
InitFormMine();
break;
}
break;
case penUpEvent:
if (isHighlighted) {
Neighbours (unHighlight, saveX, saveY);
isHighlighted = false;
}
handled = true;
break;
default:
break;
}
CALLBACK_EPILOGUE
return handled;
}
/**
* About Form
*/
static Boolean AboutFormHandleEvent(EventPtr e)
{
Boolean handled;
CALLBACK_PROLOGUE
handled = false;
switch (e->eType) {
case frmOpenEvent:
FrmDrawForm(FrmGetActiveForm());
handled = true;
break;
case ctlSelectEvent:
if (e->data.ctlSelect.controlID == ID_FrmAboutButton) {
FrmReturnToForm(0);
handled = true;
break;
}
break;
default:
break;
}
CALLBACK_EPILOGUE
return handled;
}
/**
* Preferences Form
*/
/*
* preset the radio buttons for selecting the difficulty level and
* the check box, that controls if all fields are uncovered, if you
* loose a game
*/
static void InitFormPref()
{
FormPtr frm = FrmGetActiveForm();
FrmSetControlGroupSelection(frm, 1, ID_PrefButton + game.level);
FrmSetControlValue(frm, FrmGetObjectIndex(frm, ID_PrefButton + 6),
(game.options & ShowAll));
FrmSetControlValue(frm, FrmGetObjectIndex(frm, ID_PrefButton + 7),
(game.options & AlternateControl));
// Insert by Lucas Bremgartner
FrmSetControlValue(frm, FrmGetObjectIndex(frm, ID_PrefButton + 8),
(game.options & FirstNoMine));
// Insert by Lucas Bremgartner (End)
}
/*
* handle any events in the preferences form; i.e. setting game
* difficulty level and "Uncover all" check box.
*/
static Boolean PrefFormHandleEvent(EventPtr e)
{
Boolean handled;
CALLBACK_PROLOGUE
handled = false;
switch (e->eType) {
case frmOpenEvent:
FrmDrawForm(FrmGetActiveForm());
handled = true;
break;
case ctlSelectEvent:
switch (e->data.ctlSelect.controlID) {
// look at status of radio buttons and save level information accordingly
case ID_PrefButton + 4: /* Ok */
game.level = FrmGetObjectId(FrmGetActiveForm(),
FrmGetControlGroupSelection(FrmGetActiveForm(), 1))
- ID_PrefButton;
nummines = levelmines[game.level];
FrmReturnToForm(0);
game.done = Restart;
handled = true;
break;
// don't do anything, simply return to main form
case ID_PrefButton + 5: /* Cancel */
FrmReturnToForm(0);
handled = true;
break;
// toggle "uncover all" status information
case ID_PrefButton + 6: /* Show all */
game.options ^= ShowAll;
handled = true;
break;
// select alternate "Dylan style controls"
case ID_PrefButton + 7:
game.options ^= AlternateControl;
handled = true;
break;
// Insert by Lucas Bremgartner
// select alternate "First field is never a mine"
case ID_PrefButton + 8:
game.options ^= FirstNoMine;
handled = true;
break;
// Insert by Lucas Bremgartner (End)
}
break;
default:
break;
}
CALLBACK_EPILOGUE
return handled;
}
/**
* dispatch all previously unhandled events, and set event handling
* routines for the different forms
*/
static Boolean ApplicationHandleEvent(EventPtr e)
{
Boolean handled;
CALLBACK_PROLOGUE
handled = false;
if (e->eType == frmLoadEvent) {
Word frmId = e->data.frmLoad.formID;
FormPtr frm = FrmInitForm(frmId);
FrmSetActiveForm(frm);
switch (frmId) {
case ID_FrmMine:
if (game.done == Restart)
InitFormMine();
else
ShowFormMine();
FrmSetEventHandler(frm, MineFormHandleEvent);
handled = true;
break;
case ID_FrmAbout:
FrmSetEventHandler(frm, AboutFormHandleEvent);
handled = true;
break;
case ID_FrmPreferences:
InitFormPref();
FrmSetEventHandler(frm, PrefFormHandleEvent);
handled = true;
break;
case ID_FrmHighScores:
FrmSetEventHandler(frm, HighscoresFormHandleEvent);
handled = true;
break;
}
}
CALLBACK_EPILOGUE
return handled;
}
/**
* Database (here: game status information) handling routines.
*/
static Err OpenDatabase(void)
{
UInt index = 0;
VoidHand RecHandle;
VoidPtr RecPointer;
Err err;
// Create database, if it doesn't exist, and save default game status.
if (!(pmDB = DmOpenDatabaseByTypeCreator('Data', 'tpPM', dmModeReadWrite))) {
if ((err = DmCreateDatabase(0, "PilotMinesDB", 'tpPM', 'Data', false)))
return err;
pmDB = DmOpenDatabaseByTypeCreator('Data', 'tpPM', dmModeReadWrite);
RecHandle = DmNewRecord(pmDB, &index, sizeof(game));
DmWrite(MemHandleLock(RecHandle), 0, &game, sizeof(game));
MemHandleUnlock(RecHandle);
DmReleaseRecord(pmDB, index, true);
}
// Load a saved game status.
RecHandle = DmGetRecord(pmDB, 0);
RecPointer = MemHandleLock(RecHandle);
MemMove(&game, RecPointer, sizeof(game));
MemHandleUnlock(RecHandle);
DmReleaseRecord(pmDB, 0, true);
return 0;
}
/*
* Save game status information.
*/
static void SaveStatus()
{
VoidPtr p = MemHandleLock(DmGetRecord(pmDB, 0));
game.seconds = TimGetSeconds() - GameStartedAt;
DmWrite(p, 0, &game, sizeof(game));
MemPtrUnlock(p);
DmReleaseRecord(pmDB, 0, true);
}
/*
* Main program.
*/
DWord PilotMain(Word cmd, Ptr cmdPBP, Word launchFlags)
{
if (cmd==sysAppLaunchCmdNormalLaunch) {
EventType e;
short err;
// initialize game status information
game.level = 2;
game.version = 1;
// Changed by Lucas Bremgartner
game.options = ShowAll | FirstNoMine;
// Changed by Lucas Bremgartner (End)
game.done = Restart;
game.seconds = 0;
InitHighScore();
// load a saved game
if ((err = OpenDatabase()))
return err;
// restore game status from loaded game, if necessary
nummines = levelmines[game.level];
InitBoard();
FrmGotoForm(ID_FrmMine);
do {
EvtGetEvent(&e,50);
// don't make noise when pressing PgUp/PgDn
if (e.eType == keyDownEvent &&
(e.data.keyDown.modifiers & commandKeyMask) &&
(e.data.keyDown.chr == pageUpChr || e.data.keyDown.chr == pageDownChr))
continue;
if (SysHandleEvent(&e)) {
continue;
}
if (MenuHandleEvent(NULL, &e, &err)) {
continue;
}
if (ApplicationHandleEvent(&e)) {
continue;
}
FrmDispatchEvent( &e );
} while (e.eType != appStopEvent);
FrmCloseAllForms();
SaveStatus();
DmCloseDatabase(pmDB);
WinDeleteWindow(bitmaps, false);
}
return 0;
}